/*---------------------------------------------------------------------------
 * 2005 NORSYS
 * main author : nono
 *
 * This software is a computer program whose purpose is to provide abstraction
 * for accessing directory data sources within java applications.
 *
 * This software is governed by the CeCILL  license under French law and
 * abiding by the rules of distribution of free software.  You can  use, 
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info". 
 *
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability. 
 *
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or 
 * data to be ensured and,  more generally, to use and operate it in the 
 * same conditions as regards security. 
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
 *
 * Created on Nov 15, 2005
 * --------------------------------------------------------------------------*/

package speculoos.jndi.mappers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.naming.NamingException;
import javax.naming.directory.DirContext;

import speculoos.core.MapperException;
import speculoos.jndi.SearchMapper;
import speculoos.spi.MapperTechnicalException;
import speculoos.utils.TypeHelper;

/**
 * A class that allows retrieving objects using chained query.
 * <p>
 * This class implements for directory objects what is called an
 * <em>inner join</em> in relational world: Retrieving a collection of objects
 * whose attribute equals the value of one value of a multiple attribute in
 * another object.
 * </p>
 * <p>
 * For instance, assume there exists nodes in a directory with root value
 * <code>ou=Sites,o=AcmeInc</code> and with distinguishd name
 * <code>cn=xxxx</code> where <code>xxxx</code> is the name of a unique
 * attribute among all sites. Lets assume furthermore that each site has the
 * following single-valued attributes: <code>address</code> and
 * <code>telephoneNumber</code> and that each have a multiple valued attribute
 * <code>uniqueMember</code> which holds unique identifiers of persons in this
 * site. This id uniquely identifies persons in the tree at node
 * <code>ou=Persons,o=AcmeInc</code>.
 * </p>
 * <p>
 * By setting a first search query <code>siteSearchByName</code> returning a
 * site as property <code>left</code> in this class and a second query
 * <code>personSearchById</code> as property <code>right</code>, one
 * obtains a new search query that retrieves all persons attached to one site by
 * name. The result of the first map plus the joined attributes value are used
 * as input to the second map.
 * </p>
 * <p>
 * The joined attribute is controlled by method
 * {@link #setJoinAttribute(String)} which takes an object-space attribute name.
 * </p>
 * 
 * @author nono
 * @version $Id: AttributeJoinMapperImpl.java 259 2006-05-23 10:34:50Z
 *          /C=FR/ST=Nord/L=Lille/O=Norsys SA/OU=UE/CN=Arnaud
 *          Bailly/emailAddress=abailly@norsys.fr $
 */
public class AttributeJoinMapperImpl extends JNDIMapper implements SearchMapper {

	/*
	 * name of join attribute - in first map
	 */
	private String joinAttribute;

	/*
	 * name of joined attribute - in second map
	 */
	private String joinedAttribute;

	/*
	 * left part of join : where joined attribute come from
	 */
	private SearchMapper left;

	/*
	 * right part of join: where joined attribute value are used as filter
	 * (hopefully)
	 */
	private SearchMapper right;

	/*
	 * Use only distinct values of join attribute
	 */
	private boolean distinct;

	/*
	 * for introspection
	 */
	private TypeHelper typeHelper;

	public AttributeJoinMapperImpl() {
		super("Join");
	}

	public AttributeJoinMapperImpl(String value) {
		super(value);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.jndi.SearchMapper#search(java.util.Map, java.util.Map)
	 */
	public List search(Object input, Map env) throws MapperException {
		return (List) map(input, env);
	}

	/**
	 * @return Returns the joinAttribute.
	 */
	public String getJoinAttribute() {
		return joinAttribute;
	}

	/**
	 * @param joinAttribute
	 *            The joinAttribute to set.
	 */
	public void setJoinAttribute(String joinAttribute) {
		this.joinAttribute = joinAttribute;
	}

	/**
	 * @return Returns the left.
	 */
	public SearchMapper getLeft() {
		return left;
	}

	/**
	 * @param left
	 *            The left to set.
	 */
	public void setLeft(SearchMapper left) {
		this.left = left;
	}

	/**
	 * @return Returns the right.
	 */
	public SearchMapper getRight() {
		return right;
	}

	/**
	 * @param right
	 *            The right to set.
	 */
	public void setRight(SearchMapper right) {
		this.right = right;
	}

	/**
	 * @return Returns the joinedAttribute.
	 */
	public String getJoinedAttribute() {
		return joinedAttribute;
	}

	/**
	 * @param joinedAttribute
	 *            The joinedAttribute to set.
	 */
	public void setJoinedAttribute(String joinedAttribute) {
		this.joinedAttribute = joinedAttribute;
	}

	/**
	 * @return Returns the distinct.
	 */
	public boolean isDistinct() {
		return distinct;
	}

	/**
	 * @param distinct
	 *            The distinct to set.
	 */
	public void setDistinct(boolean distinct) {
		this.distinct = distinct;
	}

	public TypeHelper getTypeHelper() {
		return typeHelper;
	}

	public void setTypeHelper(TypeHelper typeHelper) {
		this.typeHelper = typeHelper;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see speculoos.jndi.mappers.JNDIMapper#doMap(java.lang.Object,
	 *      java.util.Map)
	 */
	protected Object doMap(Object input, Map env) throws MapperException {
		/* consistency check */
		if (joinAttribute == null || "".equals(joinAttribute))
			throw new MapperException(
					"May not use a null or empty attribute name as join");
		if (joinedAttribute == null || "".equals(joinedAttribute))
			throw new MapperException(
					"May not use a null or empty attribute name as joined attribute");
		if (left == null || right == null)
			throw new MapperException("No left and right mapper defined");
		/* result */
		List res = new ArrayList();
		/* first search */
		List first = left.search(input, env);
		/* collection of joined objects */
		Collection jvals;
		if (distinct) {
			jvals = new HashSet();
		} else
			jvals = new ArrayList();
		/* construct list of join values */
		for (Iterator i = first.iterator(); i.hasNext();) {
			Object cur = i.next();
			/* retrieve join attribute value(s) */
			Object o = typeHelper.get(cur, joinAttribute);
			if (!(o instanceof Collection)) {
				jvals.add(o);
			} else
				jvals.addAll((Collection) o);
		}
		/* iterate second map over join attribute values */
		for (Iterator j = jvals.iterator(); j.hasNext();) {
			Object val = j.next();
			env.put(joinedAttribute, val);
			/* do query - must be a filtered query */
			List l = right.search(null, env);
			/* flatten result */
			res.addAll(l);
		}
		return res;
	}

	/*
	 * passes the directory to submappers
	 * @see speculoos.jndi.mappers.JNDIMapper#setDirectory(javax.naming.directory.DirContext)
	 */
	public void setDirectory(DirContext directory) throws NamingException {
		super.setDirectory(directory);
		((JNDIMapper)left).setDirectory(directory);
		((JNDIMapper)right).setDirectory(directory);
	}

	/*
	 *  (non-Javadoc)
	 * @see java.lang.Object#clone()
	 */
	public Object clone() {
		AttributeJoinMapperImpl cl = (AttributeJoinMapperImpl) super.clone();
		// clone submappers
		cl.left = (SearchMapper)left.clone();
		cl.right = (SearchMapper)right.clone();
		return cl;
	}

	/* (non-Javadoc)
	 * @see speculoos.jndi.mappers.JNDIMapper#updateRoot(java.util.Map)
	 */
	protected void updateRoot(Map vars) throws MapperTechnicalException {
		// NOP
	}

}
